C++ Primer Plus知识点

加油鸭


C++融合了3种不同的编程方式

C语言代表的过程性语言,C++在C语言基础上添加的类代表的面向对象语言(oop),C++模板支持的泛型编程。


C++11初始化方式

1
2
3
4
5
6
//大括号初始化,等号可以使用,也可以不使用
int emus{7};
int rheas={12};
//大括号内不包含任何东西,这种情况下变量将被初始化为零
int rocs={};
int psychics{};

C++存储整型的方式类似一个圈,负数存的是补码。


第一位为1-9,则基数为10(十进制),第一位为0,则基数为8(8进制),前两位为0x或0X,则基数为16。


cout的一些特殊特性

1
2
3
4
5
6
7
8
9
10
/*
cout提供了控制符dec,hex,oct分别用于指示cout以十进制,
十六进制和八进制格式显示整数。
*/
int tmp=42;
cout<<tmp<<endl;
cout<<hex;
cout<<tmp<<endl;
cout<<oct;
cout<<tmp<<endl;


C++11强制类型转换运算符static_cast < typeName>(value)

1
2
3
//使用要求更为严格
char c='c';
cout<<static_cast<int>(c)<<endl;


cin.get() 会留下换行符, cin.getline() 不会留下换行符


C++11新增的另一种类型:原始(raw)字符串

典型的不同是”\n”可以直接表示”\n”而不是换行。

1
2
cout<<R"(Jim "King" Tutt uses "\n" instead of endl.)"<<'\n';
//输出 Jim "King" Tutt uses \n instead of endl.

共用体

结构体可以同时存储int,long和double,共用体只能存储int,long或double。
共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的长度为其最大的成员的长度。


C++结构体种的位字段

C++允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或者枚举,接下来是冒号,冒号后面跟着一个数字,它制定了位数。可以使用没有名称的字段提供间距。每个成员都被称为位字段。

1
2
3
4
5
6
7
8
9
struct torgle_register
{
unsigned int SN : 4;
unsigned int : 4;
bool goodIn : 1;
bool goodTorgle : 1;
};
torgle_register tr = {14,true,false};
if(tr.goodIn)puts("Yes");


一定要在对指针应用解除引用运算符($*$) 之前,将一个确定的、适当的地址。


只能用 delete 来释放使用 new 分配的内存

1
2
3
int a=new int;
int *p=&a;
delete p; // 错误的!


delete 删除只释放指针指向的内存,但不会删除指针本身。


使用 new 创建动态数组,new 运算符返回第一个元素的地址。


指针

1
2
3
4
5
6
7
8
char *p="abc"; // 将字符串"abc"的地址赋给指针p
cout<<p<<endl;
cout<<*p<<endl;
cout<<&p<<endl;
//输出:
p: abc // 指针存储的元素
*p: a // 指针存储的首元素
&p: 0x6dfefc // 指针的首地址


指针占用多少空间

指针不同于一般变量,存的是变量的地址,在同一架构下地址长度都是相同的(cpu的最大寻址内存空间),所以不同类型的指针长度都一样。

32 位系统指针占 4 个字节。
64 位系统指针占 8 个字节。


指针和字符串

  • 如果给 cout 提供一个字符的地址,则它将从该字符开始打印,直至遇到空字符为止。如果给 cout 提供一个实数的地址,则输出地址。
  • 在 C++ 中,用引号括起的字符串像数组名一样,也是第一个元素的地址。

sizeof运算符和指针,数组之间的关系

1
2
3
4
5
6
7
char *p="abc"; 
cout<<sizeof(p)<<endl;//指针长度,一律返回4
cout<<sizeof(*p)<<endl;//字符长度,1个字节
char a[]="abcdef";
cout<<sizeof(a)<<endl;//数组长度 7
cout<<sizeof(&a)<<endl;//指针长度,一律返回4
cout<<sizeof(*a)<<endl;//字符长度,1个字节


成员运算符”.” 和 间接成员运算符”->”

成员运算符一般和结构或者联合名一起使用,指定结构或者联合中的某个成员。
间接成员运算符和指向结构或者联合的指针一起使用,标识结构或者联合中的某个成员。

“.”优先级高于”*”。

例如:$\rm (*ptd).x$


自动存储、静态存储和动态存储

  • 自动存储$:$ 常见的有局部变量,也成为自动变量,自动变量通常存储在栈中。
  • 静态存储:常见的有全局变量和 static 声明的变量,通常存储在栈中全局区(静态区)。
  • 动态存储:new 和 delete 的存储方法,通常存储在自由存储空间或堆。

类型组合(p118)

其中数组指针不明白。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct student
{
int year;
};
//变量
student a1,a2,a3;
a1.year=2003;
a2.year=2005;
//数组
student t[3];
t[0].year=2003;
(t+1)->year=2004;
//指针数组
const student *b[3]={&a1,&a2,&a3};
cout<<b[0]->year<<endl;
//数组指针
const student **c=b;
cout<<(*c)->year<<endl;
cout<<c[0]->year<<endl;

基于范围的for循环(C++11)(P152)
C++新增一种循环,基于范围(range-based)的for循环。

1
2
3
4
5
6
7
8
9
10
double prices[5]={4.99,1.99,6.87,7.99,8.49};
for(double x:prices)
cout<<x<<endl;
//要修改数组的元素,需要使用不同的循环变量语法
for(double &x:prices)
x=x*0.8;
//还可以使用基于范围的for循环和初始化列表
for(int x:{3,5,2,8,6})
cout<<x<<' ';
cout<<endl;


文件尾条件(P155)

输入来自于键盘。

在Unix中,可以在行首按下Ctrl+D来实现。

在Windows命令提示符模式下,可以在任意位置按Ctr+Z和Enter。

cin.get()有两种用法,一种ch=cin.get(),一种cin.get(ch)


写入及读取文件

不同于C标准库头文件中的函数 freopen, C++ 有自己的写入及读取文件函数 fstream。
写入: ofstream
读取: ifstream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//写入
ofstream outFile;
// ofstream fout;
outFile.open("D:\\duipai\\fish.txt");
char s[30];
scanf("%s",s);
//fout.open(s);
outFile<<"hello world"<<endl;
outFile.close();
// fout.close();
//读取
ifstream inFile;
//ifstream fin;
inFile.open("D:\\duipai\\fish.txt");
if(!inFile.is_open())
{
puts("Error");
exit(EXIT_FAILURE);
}
while(inFile>>s)
{
cout<<s<<endl;
// cout<<s<<endl;
}
inFile.close();
//fin.close();
/*
fish.txt
hello
world
*/


使用数组区间的函数(P220)
迭代器原型

1
2
3
4
5
6
7
8
9
10
11
12
int sum_arr(const int *begin,const int *end)
{
int tot=0;
for(const int *pt=begin;pt!=end;pt++)
tot=tot+*pt;
return tot;
}
int main()
{
sum[0]=1,sum[1]=2;sum[2]=3;
cout<<sum_arr(sum,sum+3);
}


const和指针

const和指针有两种组合方式,表示的含义不一样。

指向常量的指针:防止使用指针来修改所指向的值。

常量指针:防止改变指针指向的位置。

1
2
3
4
5
6
7
8
// 指向常量的指针
// pt的什么并不意味着指向的值是一个常量,而只是意味着对pt而言,这个值是一个常量,因此我们可以通过age变量来修改age的值,但不能用pt指针来修改它。但可以修搞pt,即赋一个新的地址给pt。
int age=39;
const int *pt = &age;

// C++禁止将const的地址赋给非const指针。
const int age2=30;
int *p2=&age2;//invalid

如果指针指向指针,情况更复杂。
假设涉及的是一级间接关系,则将非const指针赋给const指针是可以的。

1
2
3
// 常量指针
int a=3;
int * const b=&a;

函数指针
声明时函数指针的返回类型以及参数列表和函数必须一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
double pam(int);
double (*pf)(int);
int main()
{
pf=pam;
double x=pam(4);
double y=(*pf)(5);
cout<<y<<endl;
y=pf(5);//这样写也行
cout<<y<<endl;
}
double pam(int a)
{
return 1.0*a*a;
}


C++提供了许多新的函数特性,包括内联函数,按引用传递变量,默认的参数值,函数重载(多态)以及模板函数。


内联函数
内联函数的编译代码与其他的程序代码“内联”到一块,对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。
应有选择的使用内联函数。如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间只占一小部分。如果代码执行时间很短,则内联调用就可以节省非内联调用使用的大部分时间。


临时变量,引用参数和const(P262)

如果引用参数是const,则编译器将在下面两种情况下生成临时变量:

  • 实参的类型正确,但不是左值。
  • 实参的类型不正确,但可以转换成正确的类型。

左值: 左值参数是可被引用的数据对象,例如,变量,数组元素,结构成员,引用和解除引用的指针都是左值。非左值包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的表达式。在C语言中,左值最初指的是可出现在赋值语句左边的实体,但这是引入关键字const之前的情况。现在,常规变量和const变量都可视为左值,因为可通过地址访问他们。但常规变量属于可修改的左值,而const变量属于不可修改的左值。

简而言之,如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。解决方法是,禁止这么做。现在C++标准正是这样。
如果只是使用传递的值,而不是修改它们,因此临时变量不会造成任何不利的影响,反而会使函数在可处理的参数种类方面更通用。


继承特征

  • 派生类继承了基类的方法(如ofstream继承了ostream,ofstream可以使用ostream的一些方法)。
  • 基类引用可以指向派生类对象,而无需进行强制类型转换。原因

C++如何跟踪每一个重载函数

C++编译器将执行一些神奇的操作-名称修饰或名称矫正。它会根据函数原型中指定的形参类型对每个函数名进行加密。


宏定义

  • C语言使用 #define 提供宏定义。
  • 宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用。
  • 宏函数属于在结构中插入代码,没有返回值。
  • 宏函数参数没有类型,不进行类型检查。
  • 宏函数不要在最后加分号。

NULL 和 nullptr 区别和联系

  • 首先NULL 和 nullptr都表示空指针。常数 0 也表示空指针。
  • C++中NULL代表着0。
  • C++中$\rm void*$ 类型是不允许隐式转换成其他类型的。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现下面代码的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

void func(void* t)
{
cout << "func1" << endl;
}

void func(int i)
{
cout << "func2" << endl;
}

int main()
{
func(NULL);
func(nullptr);
return 0;
}
输出:
func2
func1

关键字extern

多个文件中使用外部变量,只需要在一个文件中包含该变量的定义,但在使用该变量的其他的所有文件中,都必须使用关键字声明他。


命名空间namespace
namespace主要用于解决“命名冲突”问题。